<!--
    by: yuanbiao 2020年12月04日
    name: x-book
    notes:
-->
<template>
  <div :class="[prefixCls, `${prefixCls}-${mode}`, {[`${prefixCls}-static`]: isStatic, 'high-performance': highPerformance}]" :style="bookStyles">
    <div v-for="(gItem,gIndex) in pageGroupList" :key="gIndex"
      :class="[
        `${prefixCls}__page`,
        {
          active: gIndex === groupActiveIndex,
          'page-first': gIndex === 0,
          'page-last': gIndex === pageGroupList.length - 1,
          'active-region-old': ((gIndex >= groupActiveIndexOld - options.preloadSize) && (gIndex <= groupActiveIndexOld + options.preloadSize)),
          'active-region': ((gIndex >= groupActiveIndex - options.preloadSize) && (gIndex <= groupActiveIndex + options.preloadSize))
        }
      ]"
      :style="{
        zIndex: gItem.zIndex,
        transitionDuration: gItem.transitionDuration,
        ...pageStyles
      }"
      v-hammer:pan="hammerHandler"
      v-hammer:panstart="hammerHandler"
      v-hammer:panend="hammerHandler"
      @transitionend="transitionendHandler"
    >
      <div v-for="(pItem, pIndex) in gItem" :key="`${gIndex}_${pIndex}`"
        :class="[
          `${prefixCls}__page_child`,
          `${prefixCls}__page_${pIndex % 2 ? 'back' : 'front'}`
        ]"
      >
        <canvas v-show="pageFileType==='pdf' && mode==='double'" ref="doubleCanvasItem" :class="[`${prefixCls}__page_child_canvas`]" :key="`double_${gIndex}_${pIndex}`"></canvas>
        <canvas v-show="pageFileType==='pdf' && mode==='single'" ref="singleCanvasItem" :class="[`${prefixCls}__page_child_canvas`]" :key="`single_${gIndex}_${pIndex}`"></canvas>
        <img v-if="pageFileType==='img' && (gIndex * groupSize + pIndex >= pageActiveIndex - 3) && (gIndex * groupSize + pIndex <= pageActiveIndex + 3)"
          :class="[`${prefixCls}__page_child_img`]"
          :src="(pItem || {}).imgUrl || undefined"
          @error="$event.target.classList.add('error')"
        >
        <div v-for="(aItem, aIndex) in ((pItem || {}).hotspots || [])"
          v-show="showHotspots"
          :key="`h${gIndex}_${pIndex}_${aIndex}`"
          :class="[`${prefixCls}__page_child_hotspot`]"
          :style="aItem.styles"
          @click="clickHotspotItem(aItem, aIndex, gItem, pIndex)"
        >
          <i :class="aItem.icon || fileTypeIconDict[aItem.type]"></i>
        </div>
        <div v-for="(aItem, aIndex) in ((pItem || {}).attachments || [])"
          v-show="showAttachments"
          :key="`a${gIndex}_${pIndex}_${aIndex}`"
          :class="[`${prefixCls}__page_child_attachment`, `${prefixCls}__page_child_attachment-${aItem.type}`]"
          :style="aItem.styles"
          @click="clickAttachmentItem(aItem, aIndex, gItem, pIndex)"
        >
          <i :class="aItem.icon || fileTypeIconDict[aItem.type]"></i>
        </div>
      </div>
    </div>
    <div v-show="pageGroupList.length===0"
      :class="[`${prefixCls}__empty`, 'text-center']"
    >
      <p><i class="xvu-iconfont xvu-book"></i></p>
      <p>暂无数据</p>
    </div>
  </div>
</template>

<script>
import Toast from '@/components/toast/index.js'
import { deepCopy, debounce } from '@/script/utils.js'
const pdfjs = require('pdfjs-dist')
pdfjs.GlobalWorkerOptions.workerSrc = require('pdfjs-dist/build/pdf.worker.min')
let pdfDocDict = {single: {}, double: {}}
let pdfUrlLoading = {single: [], double: []}
let pageLoadedList = {single: [], double: []}
let initFlag = false
// let pdfTimer = null

const prefixCls = 'x-book'
export default {
  name: 'x-book',
  props: {
    options: {
      type: Object,
      default () {
        return {
          pageFileType: 'img', // img|pdf
          mode: 'double', // 双页模式double|单页模式single
          gesture: true, // 是否支持手势翻页
          pageWidth: 420,
          pageHeight: 594,
          pageList: [],
          pageActiveIndex: 0,
          pdfScale: 1,
          preloadSize: 3,
          showHotspots: true,
          showAttachments: true,
          highPerformance: false
        }
      }
    }
  },
  data() {
    return {
      prefixCls: prefixCls,
      pageFileType: 'img',
      mode: 'double',
      gesture: true,
      pageWidth: 420,
      pageHeight: 594,
      showHotspots: true,
      showAttachments: true,
      highPerformance: false,
      pageList: [],
      pageActiveIndex: 0,
      pageActiveIndexOld: 0,
      isStatic: true,
      fileTypeIconDict: {
        note: 'xvu-iconfont xvu-note',
        h5: 'xvu-iconfont xvu-file-h5',
        pdf: 'xvu-iconfont xvu-file-doc',
        doc: 'xvu-iconfont xvu-file-doc',
        video: 'xvu-iconfont xvu-file-video',
        audio: 'xvu-iconfont xvu-file-audio',
        flash: 'xvu-iconfont xvu-file-flash',
        ppt: 'xvu-iconfont xvu-file-ppt',
        image: 'xvu-iconfont xvu-file-image'
      }
    }
  },
  computed: {
    groupSize () {
      return this.mode === 'double' ? 2 : 1
    },
    groupActiveIndex () {
      return Math.floor((this.pageActiveIndex + this.groupSize - 1) / this.groupSize)
    },
    groupActiveIndexOld () {
      return Math.floor((this.pageActiveIndexOld + this.groupSize - 1) / this.groupSize)
    },
    pageGroupList () {
      let result = []
      let len = this.pageList.length + 10
      this.pageList.forEach((v, i) => {
        // 页面分组
        let _i = Math.floor(i / this.groupSize)
        let _j = Math.floor(i % this.groupSize)
        result[_i] = result[_i] || []
        result[_i][_j] = v || null
        // 页面层级
        if (_i < this.groupActiveIndex - 1) {
          result[_i].zIndex = _i
        } else if (_i === this.groupActiveIndex - 1) {
          // 向后翻时，左侧层级高于右侧，避免翻页时闪烁
          // 向前翻时，右侧层级高于左侧，避免翻页时闪烁
          result[_i].zIndex = (this.pageActiveIndex > this.pageActiveIndexOld ? (len + 1) : _i)
        } else if (_i === this.groupActiveIndex) {
          result[_i].zIndex = len
        } else {
          result[_i].zIndex = (len - _i)
        }
        // 页面动画周期
        // let _d = (Math.min(Math.abs(this.groupActiveIndex - _i) * 25, 50) + 50) / 100
        // result[_i].transitionDuration = `${_d}s`
        result[_i].transitionDuration = '0.75s'
      })
      return result
    },
    pageActiveRange () {
      let preloadSize = parseInt(this.options.preloadSize) || 3
      let pageStart = Math.max(this.pageActiveIndex - preloadSize, 0)
      let pageEnd = Math.min(this.pageActiveIndex + preloadSize, this.pageList.length - 1)
      return [pageStart, pageEnd]
    },
    bookStyles () {
      let pageWidth = this.pageWidth * this.groupSize
      return {
        width: `${pageWidth}px`,
        height: `${this.pageHeight}px`
      }
    },
    pageStyles () {
      return {
        width: `${this.pageWidth}px`,
        height: `${this.pageHeight}px`
      }
    }
  },
  watch: {
    options: {
      handler (n) {
        this.mergeOptions(n)
      },
      immediate: true,
      deep: true
    },
    pageActiveRange: {
      handler () {
        this.pageActiveRangeChange()
      },
      // immediate: true,
      deep: true
    }
  },
  beforeDestroy() {
    // clearTimeout(pdfTimer)
  },
  methods: {
    mergeOptions (data) {
      data = data || {}
      Object.keys(data).forEach(k => {
        switch (k) {
          case 'mode':
            if (data[k] !== this[k]) {
              this[k] = (data[k] === 'double' ? 'double' : 'single')
              this.modeChange()
            }
            break
          case 'pageWidth':
          case 'pageHeight':
          case 'pageFileType':
            this[k] = data[k]
            break
          case 'pageActiveIndex':
            if (!initFlag) {
              this[k] = data[k]
              initFlag = true
            }
            break
          case 'gesture':
          case 'showAttachments':
          case 'showHotspots':
          case 'highPerformance':
            this[k] = !!data[k]
            break
          case 'pageList':
            this.formatPageList(data[k])
            break
        }
      })
      console.log('mergeOptions:', this.pageActiveIndex, this.pageActiveRange)
      this.pageActiveRangeChange()
    },
    // 格式化pageList数据
    formatPageList (pageList) {
      pageList = deepCopy(pageList || [])
      pageList = pageList.filter(v => !!v)
      // let len = pageList.length
      // 偶数页数校验·确保末页封皮在纸张反面
      // if (len > 2 && len % 2) {
      //   pageList.splice(len - 1, 0, null)
      // }
      // 添加序号标识
      pageList = pageList.map((v, i) => {
        if (v) v._pageNum = i
        return v
      })
      this.pageList = pageList
    },
    pageActiveRangeChange() {
      if (this.pageFileType === 'pdf') {
        let taskList = []
        for(let i = this.pageActiveRange[0]; i <= this.pageActiveRange[1]; i++) {
          if (!pageLoadedList[this.mode].includes(i)) {
            taskList.push(i)
          }
        }
        this.$nextTick(() => {
          this.renderPdfActive(taskList)
        })
      }
    },
    renderPdfActive(list) {
      if (this.options.pageList.length === 0) return
      let _this = this
      let taskList = []
      list = list || []
      list.forEach(i => {
        let v = _this.options.pageList[i]
        if (v && v.renderUrl && !pdfDocDict[this.mode][v.renderUrl] && !pdfUrlLoading[this.mode].includes(v.renderUrl)) {
          pdfUrlLoading[this.mode].push(v.renderUrl)
          taskList.push(
            pdfjs.getDocument(v.renderUrl).promise.then(pdf => {
              pdfDocDict[this.mode][v.renderUrl] = pdf
              pdfUrlLoading[this.mode] = pdfUrlLoading[this.mode].filter(url => url !== v.renderUrl)
            })
          )
        }
      })
      this.$emit('loadingStart', 'loadpdf')
      Promise.all(taskList).then(() => {
        list.forEach(i => {
          let v = _this.options.pageList[i]
          if (!v.renderUrl || !pdfDocDict[this.mode][v.renderUrl]) return
          let renderIndex = parseInt(v.renderIndex) || i + 1
          this.$emit('loadingStart', 'loadpage')
          pdfDocDict[this.mode][v.renderUrl].getPage(renderIndex).then(page => {
            let scale = parseFloat(this.options.pdfScale) || 1
            let viewport = page.getViewport({ scale: scale })
            // Prepare canvas using PDF page dimensions
            let canvas = _this.$refs[this.mode === 'double' ? 'doubleCanvasItem' : 'singleCanvasItem'][i]
            let context = canvas.getContext('2d')
            canvas.height = viewport.height
            canvas.width = viewport.width
            var renderContext = {
              canvasContext: context,
              viewport: viewport
            }
            page.render(renderContext)
            pageLoadedList[this.mode].push(i)
          }).finally(() => {
            this.$emit('loadingEnd', 'loadpage')
          })
        })
      }).finally(() => {
        this.$emit('loadingEnd', 'loadpdf')
      })
    },
    // 点击附件
    clickAttachmentItem: debounce(function (aItem, aIndex, gItem, pIndex) {
      this.$emit('clickAttachment', aItem, aIndex, gItem, pIndex)
    }, 200),
    // 点击热区
    clickHotspotItem: debounce(function (aItem, aIndex, gItem, pIndex) {
      this.$emit('clickHotspotItem', aItem, aIndex, gItem, pIndex)
    }, 200),
    // 手势翻页
    hammerHandler (event) {
      if (!this.gesture) return
      let target = event.target || event.srcElement || {}
      let classList = [...(target.classList || [])]
      if (!classList.includes(`${this.prefixCls}__page`)) return
      switch (event.type) {
        case 'panend':
          if (event.additionalEvent === 'panleft') this.next()
          else if (event.additionalEvent === 'panright') this.prev()
          break
      }
    },
    transitionendHandler() {
      this.isStatic = true
    },
    modeChange () {
      // emit
      let index = this.groupActiveIndex * this.groupSize
      let pagesActive = [this.pageList[index]]
      if (this.mode === 'double' && index > 0) {
        pagesActive.unshift(this.pageList[index - 1])
      }
      this.$emit('modeChange', this.pageActiveIndex, pagesActive)
      pdfDocDict = {single: {}, double: {}}
      pdfUrlLoading = {single: [], double: []}
      pageLoadedList = {single: [], double: []}
    },
    // 跳转到指定页面
    jump (pageNum, type) {
      let pageMax = this.mode === 'double' ? this.pageList.length : (this.pageList.length - 1)
      let pageMin = 0
      let target = Math.min(pageMax, Math.max(pageMin, pageNum))
      if (this.pageList.length <= 0) return
      if (target === this.pageActiveIndex) {
        if (target === 0 && type === 'prev') Toast('已经是第一页了')
        else if (target === pageMax && type === 'next') Toast('已经是最后一页了')
        return
      }
      this.isStatic = false
      this.pageActiveIndexOld = this.pageActiveIndex
      this.pageActiveIndex = target
      // emit
      let index = this.groupActiveIndex * this.groupSize
      let pagesActive = [this.pageList[index]]
      if (this.mode === 'double' && index > 0) {
        pagesActive.unshift(this.pageList[index - 1])
      }
      this.$emit('pageChange', target, pagesActive)
    },
    // 上一页
    prev () {
      this.jump(this.pageActiveIndex - this.groupSize, 'prev')
    },
    // 下一页
    next () {
      this.jump(this.pageActiveIndex + this.groupSize, 'next')
    },
    // 暴露方法·获取页面序号
    getPageNum () {
      return this.pageActiveIndex
    }
  }
}
</script>
